//
//  RenderBaseMVP3D.m
//  metalloader3D
//
//  Created by lrj on 2023/2/19.
//
@import simd;
@import ModelIO;
@import MetalKit;
@import GLKit;
#import "AAPLMesh.h"
#import "AAPLMathUtilities.h"
#import "ShaderTypes.h"
#import "RenderBaseMVP3D.h"

@implementation RenderBaseMVP3D
{
    id <MTLDevice> _device;
    id <MTLCommandQueue> _commandQueue;

    MTLVertexDescriptor *_defaultVertexDescriptor;

    id <MTLRenderPipelineState> _pipelineState;

    id <MTLDepthStencilState> _relaxedDepthState;
    id <MTLBuffer> _uniformBuffer;

    matrix_float4x4 _projectionMatrix;
    float _rotation;

    NSArray<AAPLMesh *> *_meshes;
    
    GLKVector3 eyePosition;
    GLKVector3 lookAtPosition;
    GLKVector3 targetEyePosition;
    GLKVector3 targetLookAtPosition;
    CGFloat ZC;
    CGFloat TC;

}
- (instancetype)initWithMetalKitView:(MTKView *)view
{
    self = [super init];
    if(self){
        _device = view.device;
        [self loadMetalWithMetalKitView:view];
        [self loadAssets];
        
       eyePosition = GLKVector3Make(0, 0, 1000);
       lookAtPosition = GLKVector3Make(0.0, 0.0, 0.0);
       targetEyePosition = GLKVector3Make(0,
                                                0,
                                                1000);
       targetLookAtPosition = GLKVector3Make(0,
                                                   0,
                                                   0);
    }
    return  self;
}

GLfloat SceneScalarFastLowPassFilter(NSTimeInterval elapsed,
                                     GLfloat target,
                                     GLfloat current)
{
    return current + (50.0 * elapsed * (target - current));
}

GLfloat SceneScalarSlowLowPassFilter(NSTimeInterval elapsed,
                                     GLfloat target,
                                     GLfloat current)
{
    CGFloat time =  elapsed;
    if(time >= 1) time = 1;
    GLfloat p =  current + (time *(target - current));
    return p;
}


// 加载模型
- (void)loadAssets{
    NSError *error = nil;
    MDLVertexDescriptor *modelIOVertexDescriptor =
        MTKModelIOVertexDescriptorFromMetal(_defaultVertexDescriptor);
    //顶点
    modelIOVertexDescriptor.attributes[0].name  = MDLVertexAttributePosition;
    //纹理
    modelIOVertexDescriptor.attributes[1].name  = MDLVertexAttributeTextureCoordinate;
    //各种其他的顶点
    //modelIOVertexDescriptor.attributes[2].name    = MDLVertexAttributeNormal;
    //modelIOVertexDescriptor.attributes[3].name   = MDLVertexAttributeTangent;
    //modelIOVertexDescriptor.attributes[4].name = MDLVertexAttributeBitangent;
    
    NSURL *modelFileURL = [[NSBundle mainBundle] URLForResource:@"Temple.obj" withExtension:nil];
    NSAssert(modelFileURL, @"Could not find model (%@) file in bundle", modelFileURL.absoluteString);
    _meshes = [AAPLMesh newMeshesFromURL:modelFileURL
                 modelIOVertexDescriptor:modelIOVertexDescriptor
                             metalDevice:_device
                                   error:&error];
    
    NSAssert(_meshes, @"Could not find model (%@) file in bundle", error);
}


- (void)loadMetalWithMetalKitView:(nonnull MTKView *)view{
    view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
    view.colorPixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
    view.sampleCount = 1;
    
    _rotation = 0;
    
    id <MTLLibrary> defaultLibrary = [_device newDefaultLibrary];

    const MTLResourceOptions storageMode = MTLResourceStorageModeShared;
    _uniformBuffer = [_device newBufferWithLength:sizeof(Uniforms)
                                                  options:storageMode];
    
    _defaultVertexDescriptor = [[MTLVertexDescriptor alloc] init];

    // Positions.
    _defaultVertexDescriptor.attributes[0].format = MTLVertexFormatFloat3;
    _defaultVertexDescriptor.attributes[0].offset = 0;
    _defaultVertexDescriptor.attributes[0].bufferIndex = 0;

    // Texture coordinates.
    _defaultVertexDescriptor.attributes[1].format = MTLVertexFormatFloat2;
    _defaultVertexDescriptor.attributes[1].offset = 12;
    _defaultVertexDescriptor.attributes[1].bufferIndex = 0;
    
    // ...

    _defaultVertexDescriptor.layouts[0].stride = 44;
    _defaultVertexDescriptor.layouts[0].stepRate = 1;
    _defaultVertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;

    id <MTLFunction> vertexStandardMaterial = [defaultLibrary newFunctionWithName:@"vertexShader2"];
    id <MTLFunction> fragmentStandardMaterial = [defaultLibrary newFunctionWithName:@"fragmentShader2"];
    
    // Create a render pipeline state descriptor.
    MTLRenderPipelineDescriptor * renderPipelineStateDescriptor = [MTLRenderPipelineDescriptor new];

    renderPipelineStateDescriptor.label = @"Forward Lighting";
    renderPipelineStateDescriptor.sampleCount = view.sampleCount;
    renderPipelineStateDescriptor.vertexDescriptor = _defaultVertexDescriptor;
    renderPipelineStateDescriptor.vertexFunction = vertexStandardMaterial;
    renderPipelineStateDescriptor.fragmentFunction = fragmentStandardMaterial;
    renderPipelineStateDescriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat;
    renderPipelineStateDescriptor.depthAttachmentPixelFormat = view.depthStencilPixelFormat;
    renderPipelineStateDescriptor.stencilAttachmentPixelFormat = view.depthStencilPixelFormat;
    
    NSError* error = NULL;
    _pipelineState = [_device newRenderPipelineStateWithDescriptor:renderPipelineStateDescriptor
                                       error:&error];
        
    NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error);

    MTLDepthStencilDescriptor *depthStateDesc = [[MTLDepthStencilDescriptor alloc] init];

    {
        depthStateDesc.depthCompareFunction = MTLCompareFunctionLessEqual;
        depthStateDesc.depthWriteEnabled = YES;
        _relaxedDepthState = [_device newDepthStencilStateWithDescriptor:depthStateDesc];
    }
    _commandQueue = [_device newCommandQueue];
    
    
}



- (void)updateGameState{
    Uniforms * uniforms = (Uniforms*)_uniformBuffer.contents;
    uniforms->projectionMatrix = _projectionMatrix;
//    matrix_float4x4 viewMatrixT = matrix_multiply(matrix4x4_translation(0.0, 0, 1000),
//                                           matrix_multiply(matrix4x4_rotation(-0.5, (vector_float3){1,0,0}),
//                                                           matrix4x4_rotation(_rotation, (vector_float3){0,1,0} )));
    matrix_float4x4 viewMatrixT = matrix_multiply(matrix4x4_translation(0.0, 0, 1000),
                                           matrix_multiply(matrix4x4_rotation(0.0, (vector_float3){1,0,0}),
                                                           matrix4x4_rotation(0, (vector_float3){0,1,0} )));
   static  CGFloat xt = 0.05;
    xt += 0.01;
    GLKMatrix4 viewMatrixP = GLKMatrix4MakeLookAtP(0, SceneScalarSlowLowPassFilter(xt, -200, 0), SceneScalarSlowLowPassFilter(xt, 410, 1200), 0, 0, 0, 0, 1, 0);
//    GLKMatrix4 viewMatrixP = GLKMatrix4MakeLookAtP(0, -200, 410, 0, 0, 0, 0, 1, 0);
    matrix_float4x4 viewMatrix = [self getMetalMatrix4FromGLKMatrix4:viewMatrixP];
    viewMatrix = matrix_multiply(viewMatrix, matrix_multiply(matrix4x4_rotation(-0.5, (vector_float3){1,0,0}),matrix4x4_rotation(_rotation, (vector_float3){0,1,0} )));
    vector_float3 rotationAxis = {0, 1, 0};
    matrix_float4x4 modelMatrix = matrix4x4_rotation(0, rotationAxis);
    matrix_float4x4 translation = matrix4x4_translation(0.0, 0, 0);
    modelMatrix = matrix_multiply(modelMatrix, translation);

    uniforms->modelViewMatrix = matrix_multiply(viewMatrix, modelMatrix);

    _rotation += 0.002f;
    
}

GLKMatrix4 GLKMatrix4MakeLookAtP(float eyeX, float eyeY, float eyeZ,
                                                  float centerX, float centerY, float centerZ,
                                                  float upX, float upY, float upZ)
{
    GLKVector3 ev = {{ eyeX, eyeY, eyeZ }};
    GLKVector3 cv = {{ centerX, centerY, centerZ }};
    GLKVector3 uv = {{ upX, upY, upZ }};
    GLKVector3 n = GLKVector3Normalize(GLKVector3Add(ev, GLKVector3Negate(cv)));
    GLKVector3 u = GLKVector3Normalize(GLKVector3CrossProduct(uv, n));
    GLKVector3 v = GLKVector3CrossProduct(n, u);
    CGFloat x =  GLKVector3DotProduct(GLKVector3Negate(v), ev);
    GLKMatrix4 m = {{ u.v[0], v.v[0], n.v[0], 0.0f,
        u.v[1], v.v[1], n.v[1], 0.0f,
        u.v[2], v.v[2], n.v[2], 0.0f,
        GLKVector3DotProductX((u), ev),
        GLKVector3DotProductX((v), ev),
        GLKVector3DotProductX((n), ev),
        1.0f }};
    
    return m;
}
float GLKVector3DotProductX(GLKVector3 vectorLeft, GLKVector3 vectorRight)
{
    return vectorLeft.v[0] * vectorRight.v[0] + vectorLeft.v[1] * vectorRight.v[1] + vectorLeft.v[2] * vectorRight.v[2];
}




- (matrix_float4x4)getMetalMatrix4FromGLKMatrix4:(GLKMatrix4)matrix {
    return (matrix_float4x4) {
        simd_make_float4(matrix.m00, matrix.m01, matrix.m02, matrix.m03),
        simd_make_float4(matrix.m10, matrix.m11, matrix.m12, matrix.m13),
        simd_make_float4(matrix.m20, matrix.m21, matrix.m22, matrix.m23),
        simd_make_float4(matrix.m30, matrix.m31, matrix.m32, matrix.m33),
    };
}


- (void) mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
{
    float aspect = size.width / (float)size.height;
    float _fov = 65.0f * (M_PI / 180.0f);
    float _nearPlane = 1.0f;
    float _farPlane = 1500.0f;
    _projectionMatrix = matrix_perspective_left_hand(_fov, aspect, _nearPlane, _farPlane);
}

- (void) drawInMTKView:(nonnull MTKView *)view{
    id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    commandBuffer.label = @"MyCommand";
    [self updateGameState];
    
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    
    if(renderPassDescriptor != nil)
    {
        id <MTLRenderCommandEncoder> renderEncoder =
            [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];

        [renderEncoder setCullMode:MTLCullModeBack];

        [renderEncoder pushDebugGroup:@"Render Forward Lighting"];
        [renderEncoder setRenderPipelineState:_pipelineState];
        [renderEncoder setDepthStencilState:_relaxedDepthState];
        [renderEncoder setVertexBuffer:_uniformBuffer offset:0 atIndex:1];
        [self drawMeshes:renderEncoder];
        [renderEncoder popDebugGroup];

        [renderEncoder endEncoding];
    }

    // Schedule a presentation for the current drawable, after the framebuffer is complete.
    [commandBuffer presentDrawable:view.currentDrawable];

    // Finalize rendering here and send the command buffer to the GPU.
    [commandBuffer commit];
}






/// Draw the mesh objects with the given render command encoder.
- (void)drawMeshes:(id<MTLRenderCommandEncoder>)renderEncoder
{
    //__unsafe_unretained 提高性能，弱引用，当确定对象一定存在的时候
    for (__unsafe_unretained AAPLMesh *mesh in _meshes)
    {
        __unsafe_unretained MTKMesh *metalKitMesh = mesh.metalKitMesh;

        // Set the mesh's vertex buffers.
        for (NSUInteger bufferIndex = 0; bufferIndex < metalKitMesh.vertexBuffers.count; bufferIndex++)
        {
            __unsafe_unretained MTKMeshBuffer *vertexBuffer = metalKitMesh.vertexBuffers[bufferIndex];
            if((NSNull*)vertexBuffer != [NSNull null])
            {
                [renderEncoder setVertexBuffer:vertexBuffer.buffer
                                        offset:vertexBuffer.offset
                                       atIndex:bufferIndex];
            }
        }

        // Draw each submesh of the mesh.
        for(AAPLSubmesh *submesh in mesh.submeshes)
        {
            // Set any textures that you read or sample in the render pipeline.
            [renderEncoder setFragmentTexture:submesh.textures[0]
                                      atIndex:0];

            [renderEncoder setFragmentTexture:submesh.textures[1]
                                      atIndex:1];

            [renderEncoder setFragmentTexture:submesh.textures[2]
                                      atIndex:2];

            MTKSubmesh *metalKitSubmesh = submesh.metalKitSubmmesh;

            //索引绘制
            [renderEncoder drawIndexedPrimitives:metalKitSubmesh.primitiveType
                                      indexCount:metalKitSubmesh.indexCount
                                       indexType:metalKitSubmesh.indexType
                                     indexBuffer:metalKitSubmesh.indexBuffer.buffer
                               indexBufferOffset:metalKitSubmesh.indexBuffer.offset];
        }
    }
}
@end
